﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;

namespace LabMonitorRemote
{
    public class LoggerDownload : INotifyPropertyChanged
    {
        Int32 bytesDownloaded;
        Int32 totalBytes;
        Int32 packetSize;
        string loggerIPAddress;
        string loggerName;
        IPEndPoint loggerIPEndpoint;
        const Int32 port = 10001;
        DateTime fromDate;
        DateTime throughDate;
        BinaryFileWriter BinWriter;
        Boolean cancelDownload = false;
        Boolean networkCommActive;
        frmMain _ParentForm;

        public LoggerDownload(DewkLogger Dewk, DateTime FromDate, DateTime ThroughDate, Int32 NetworkPacketSize = 1024)
        {
            this.LoggerName = Dewk.LoggerName;
            this.LoggerIPAddress = Dewk.IpAddress;
            this.DownloadFromDate = FromDate;
            this.DownloadThroughDate = ThroughDate;
            this.PacketSize = NetworkPacketSize;
            BinWriter = new BinaryFileWriter(LoggerName, loggerIPEndpoint);
        }

        public LoggerDownload(string LoggerName, string LoggerIPAddress, DateTime FromDate, DateTime ThroughDate, Int32 NetworkPacketSize = 1024)
        {
            this.LoggerName = LoggerName;
            this.LoggerIPAddress = LoggerIPAddress;
            this.DownloadFromDate = FromDate;
            this.DownloadThroughDate = ThroughDate;
            this.PacketSize = NetworkPacketSize;
            BinWriter = new BinaryFileWriter(LoggerName, loggerIPEndpoint);
        }

        // EVENT HANDLERS
        #region Event Handlers
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            //PropertyChangedEventHandler handler = PropertyChanged;
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                //handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public event NetworkCommActivityChangedEventHandler NetworkCommActivityChanged;
        public delegate void NetworkCommActivityChangedEventHandler(LoggerDownload ld, EventArgs e);
        private void OnNetworkCommActivityChanged(string stuff)  
        {
            //PropertyChangedEventHandler handler = PropertyChanged;
            if (NetworkCommActivityChanged != null)
            {
                NetworkCommActivityChanged(this, new EventArgs());
                //handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public event DatasetRequestedEventHandler DatasetRequested;
        public delegate void DatasetRequestedEventHandler(LoggerDownload ld, EventArgs e);
        private void OnDatasetRequested()
        {
            if (DatasetRequested != null)
            {
                DatasetRequested(this, new EventArgs());
            }
        }

        public event BytesChangedEventHandler BytesChanged;
        public delegate void BytesChangedEventHandler(LoggerDownload ld, EventArgs e);
        private void OnBytesChanged(string bytes)
        {
            if (BytesChanged != null)
            {
                BytesChanged(this, new EventArgs());
            }
        }

        public event TotalBytesChangedEventHandler TotalBytesChanged;
        public delegate void TotalBytesChangedEventHandler(LoggerDownload ld, EventArgs e);
        private void OnTotalBytesChanged(string bytes)
        {
            if (TotalBytesChanged != null)
            {
                TotalBytesChanged(this, new EventArgs());
            }
        }

        public event DownloadCompleteEventHandler DownloadComplete;
        public delegate void DownloadCompleteEventHandler(LoggerDownload ld, EventArgs e);
        private void OnDownloadComplete(string bytes)
        {
            if (DownloadComplete != null)
            {
                DownloadComplete(this,new EventArgs());
            }
        }

        public event ConnectionFailedEventHandler ConnectionFailed;
        public delegate void ConnectionFailedEventHandler(LoggerDownload ld, EventArgs e);
        private void OnConnectionFailed()
        {
            if (ConnectionFailed != null)
            {
                ConnectionFailed(this, new EventArgs());
            }
        }

        public event DownloadCanceledEventHandler DownloadCanceled;
        public delegate void DownloadCanceledEventHandler(LoggerDownload ld, EventArgs e);
        private void OnDownloadCanceled()
        {
            if (DownloadCanceled != null)
            {
                DownloadCanceled(this, new EventArgs());
            }
        }

        #endregion Event Handlers
        // EVENT HANDLERS

        // PROPERTIES    
        #region Properties 

        public frmMain ParentForm
        {
            get { return _ParentForm; }
            set { _ParentForm = value; }
        }

        public Int32 PacketSize
        {
            get { return packetSize; }
            private set
            {
                if (value > 127 && value < 8097)
                {
                    packetSize = value;
                }
                else
                    packetSize = 1024;
            }
        }
        public DateTime DownloadFromDate
        {
            get { return fromDate; }
            private set 
            {
                fromDate = value;
            }
        }
        public DateTime DownloadThroughDate
        {
            get { return throughDate; }
            private set 
            {
                throughDate = value;
            }
        }
        public Int32 NumBytesDownloaded
        {
            get { return bytesDownloaded; }
            private set 
            {
                bytesDownloaded = value;
                OnBytesChanged(bytesDownloaded.ToString());
                if (this.NumBytesDownloaded == this.TotalBytes)
                    OnDownloadComplete(bytesDownloaded.ToString());
            }
        }
        public Int32 TotalBytes
        {
            get { return totalBytes; }
            private set 
            { 
                totalBytes = value;
                OnTotalBytesChanged(totalBytes.ToString());
            }
        }
        public string LoggerName
        {
            get { return loggerName; }
            set { loggerName = value; }
        }
        public string LoggerIPAddress
        {
            get { return loggerIPAddress; }
            private set 
            {
                if (ValidIPAddress(value))
                {
                    loggerIPAddress = value;
                    loggerIPEndpoint = new IPEndPoint(IPAddress.Parse(value), port);
                }
                else
                {
                    Console.WriteLine("The address entered {0} is not a valid IPv4 address.", value);
                }
            }
        }

        public Boolean NetworkCommActive
        {
            get { return networkCommActive; }
            set
            {
                if (networkCommActive != value)
                {
                    networkCommActive = value;
                    OnNetworkCommActivityChanged("stuff");
                }
            }
        }

        #endregion
        // END PROPERTIES

        public void CancelDownload()
        {
            cancelDownload = true;
            OnDownloadCanceled();
        }

        private void RespondToConnectionFailed(object sender, EventArgs e)
        {
            cancelDownload = true;
            string errorMessage = string.Format("ERROR: {0} {1} Connection failed !!!", this.LoggerName, this.LoggerIPAddress);
            this.ParentForm.WriteNetworkFeedback(errorMessage);
            OnConnectionFailed();
        }

        private void ValidDate(DateTime date)
        {
            // method not yet implemented
            throw new Exception("@Method not implemented");        
        }

        private bool ValidIPAddress(string ip)
        {
            string[] octets = ip.Split(".".ToCharArray());
            if (octets.Length < 4)
            {
                // not a valid IPv4 string in X.X.X.X format
                return false;
            }
            else
                foreach (string octet in octets)
                {
                    byte checkPart = 0;
                    if (!byte.TryParse(octet, out checkPart))
                    {
                        // not a valid IPv4 string in X.X.X.X format
                        return false;
                    }                    
                }
            // string is a valid IPv4 string in X.X.X.X format
            return true;
        }
        
        public void InitiateDownload()
        {
            this.NetworkCommActive = true;
            
            // Test Communications before attempting download
            DewkPing dewkPing = new DewkPing(this.LoggerName, this.LoggerIPAddress, this.ParentForm);
            dewkPing.CallingLoggerDownload = this; // will register DewkPink event handlers with this LoggerDownload
            dewkPing.TestCommunications();
            if (dewkPing.IsConnected)
            {
                OpenDataRange();
                GetDataSetSize();

                while (this.NumBytesDownloaded < this.TotalBytes && !cancelDownload)
                {
                    ReadDataBlock();
                    Console.WriteLine("{0} of {1} bytes downloaded ...", NumBytesDownloaded, TotalBytes);
                }

                this.NetworkCommActive = false;

                if (this.NumBytesDownloaded == this.TotalBytes)
                {
                    try
                    {
                        BinWriter.WriteDataToFile();
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("Error occcured when attemping to call BinaryFileWriter.WriteDataToFile()");
                        Console.WriteLine(ex);
                    }
                }
                else
                {
                    //throw new Exception("@An error occured when preparing to write the binary data to file.");
                    Console.WriteLine("{0} bytes downloaded != bytes available or download canceled", LoggerName);
                }
            }
            else
            {
                OnConnectionFailed();
                this.NetworkCommActive = false;
                // FIRE NOT CONNECTED EVENT
            }
        } // errors to here if download is canceled

        private void ShowNetworkWrite(DewkCommand dc, EventArgs e)
        {
            if (this.ParentForm != null)
                this.ParentForm.WriteNetworkFeedback(this.LoggerName, dc.CommandToSend);
        }

        private void ShowNetworkRead(DewkRead dr, EventArgs e)
        {
            if (this.ParentForm != null)
                this.ParentForm.WriteNetworkFeedback(this.LoggerName, dr.ReadBuffer, dr.NumBytesRead);
        }

        private void ReadDataBlock()
        {
            // send command to transmit datablock from logger
            RequestDataBlockDewkCommand BlockRequest = new RequestDataBlockDewkCommand(loggerIPEndpoint, 1024);
            BlockRequest.CommandSent += new DewkCommand.CommandSentEventHandler(ShowNetworkWrite);
            BlockRequest.WriteCommand(); // ASync
            
            // read the block of data sent from the logger
            ReadDataBlockDewkRead ReadBlock = new ReadDataBlockDewkRead(BlockRequest.TCPClient, BlockRequest.NetStream, 1024);
            ReadBlock.ReadComplete += new DewkRead.ReadCompleteEventHandler(ShowNetworkRead);
            ReadBlock.ConnectionFailed += new DewkRead.ConnectionFailedEventHandler(RespondToConnectionFailed);
            ReadBlock.Read();

            // unsubscrbe
            BlockRequest.CommandSent -= new DewkCommand.CommandSentEventHandler(ShowNetworkWrite);
            ReadBlock.ReadComplete -= new DewkRead.ReadCompleteEventHandler(ShowNetworkRead);
            ReadBlock.ConnectionFailed -= new DewkRead.ConnectionFailedEventHandler(RespondToConnectionFailed);

            while (!ReadBlock.DataBlockHeaderComplete && !cancelDownload)
            {
                // send RESEND command to transmit datablock from logger
                RequestDataBlockResendDewkCommand ResendCommand = new RequestDataBlockResendDewkCommand(loggerIPEndpoint);
                ResendCommand.CommandSent += new DewkCommand.CommandSentEventHandler(ShowNetworkWrite);
                ResendCommand.WriteCommand();

                // read the block of data sent from the logger
                ReadBlock = new ReadDataBlockDewkRead(ResendCommand.TCPClient, ResendCommand.NetStream, 1024);
                ReadBlock.ReadComplete += new DewkRead.ReadCompleteEventHandler(ShowNetworkRead);
                ReadBlock.ConnectionFailed += new DewkRead.ConnectionFailedEventHandler(RespondToConnectionFailed);
                ReadBlock.Read();

                // unsubscribe
                ResendCommand.CommandSent -= new DewkCommand.CommandSentEventHandler(ShowNetworkWrite);
                ReadBlock.ReadComplete -= new DewkRead.ReadCompleteEventHandler(ShowNetworkRead);
                ReadBlock.ConnectionFailed -= new DewkRead.ConnectionFailedEventHandler(RespondToConnectionFailed);
            }

            // Send data block to Binary File Writer
            this.NumBytesDownloaded += ReadBlock.DataBlockSize;
            BinWriter.AppendBytes(ReadBlock.ReadBuffer, ReadBlock.PreDataBytesSize, ReadBlock.DataBlockSize + ReadBlock.PreDataBytesSize);
        }
        private void GetDataSetSize()
        {
            RequestDatasetSizeDewkCommand getDataSetSize = new RequestDatasetSizeDewkCommand(loggerIPEndpoint);
            getDataSetSize.CommandSent += new DewkCommand.CommandSentEventHandler(ShowNetworkWrite);
            getDataSetSize.WriteCommand();

            ReadDataSetSizeDewkRead readBytes = new ReadDataSetSizeDewkRead(getDataSetSize.TCPClient, getDataSetSize.NetStream);
            readBytes.CallingLoggerDownload = this;
            readBytes.ReadComplete += new DewkRead.ReadCompleteEventHandler(ShowNetworkRead);
            readBytes.Read();
            Console.WriteLine("\nThe dataset is {0} bytes", readBytes.DataSetSize);
            this.TotalBytes = readBytes.DataSetSize;
            //totalBytes = readBytes.DataSetSize;
            bytesDownloaded = 0;
            readBytes.ReadComplete -= new DewkRead.ReadCompleteEventHandler(ShowNetworkRead);
        }
        private void OpenDataRange()
        {
            OpenDateRangeDewkCommand openRange = new OpenDateRangeDewkCommand(loggerIPEndpoint, DownloadFromDate, DownloadThroughDate);
            openRange.CommandSent += new DewkCommand.CommandSentEventHandler(ShowNetworkWrite);
            openRange.WriteCommand();
            openRange.ManuallyCloseClient();
            openRange.CommandSent -= new DewkCommand.CommandSentEventHandler(ShowNetworkWrite);
            OnDatasetRequested();
        }
    }
}
